/* 
    sample code for MPEG4 for 7113 Composite input and MPEG4 output
    This sample code is to do encode 100 stream files named "/tmp/dev0.m4v"
 */
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/timeb.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/ipc.h>
#include <sys/socket.h>
#include <sys/resource.h>
#include <sys/time.h>
#include <sys/types.h>
#include <linux/videodev.h>
#include <errno.h>
#include <linux/fb.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/un.h>
#include <signal.h>

#include "fmpeg4_avcodec.h"
#include "ratecontrol.h"

#define FMPEG4_ENCODER_DEV  "/dev/fenc"
#define devicename "/dev/video0"
#define dout_name "/tmp/share/dev0.m4v"
#define dout_roi_name "/tmp/share/dev0_roi.m4v"

#define NEW_VERSION

typedef struct AVFrame {
    uint8_t *data[4];
} AVFrame;


typedef struct video_profile
{
    unsigned int bit_rate;
    unsigned int width;   //length per dma buffer
    unsigned int height;
    unsigned int framerate;
    unsigned int frame_rate_base;
    unsigned int gop_size;
    unsigned int qmax;
    unsigned int qmin;   
    unsigned int quant;
    unsigned int intra;
    unsigned int roi_enable;
    unsigned int roi_x;
    unsigned int roi_y;	
    unsigned int roi_width;
    unsigned int roi_height;	
    AVFrame *coded_frame;
    char *priv;
} video_profile;


FILE            *dout;
int             fmpeg4_enc_fd = 0;
RateControl     ratec;
video_profile   video_setting;

/*  
#################################################
    capture 
################################################# 
 */
typedef struct fcap_frame_buff
{
    unsigned int phyAddr;
    unsigned int mmapAddr;   //length per dma buffer
    unsigned int frame_no;
}fcap_frame_buff_t;


int                     video_fd=-1;
struct video_mbuf       gb_buffers;
struct video_mmap       gb_buf;
struct video_capability video_cap;
struct video_window     win;
struct video_tuner      tuner;  
unsigned char           *video_buf_virt;
unsigned char           *out_virt_buffer;
unsigned char           *mmap_addr;
unsigned int            mmap_done=0;
int                     cap_type = 0;
int 		       ingrab = 14;
int                     frame_num = 0;
int                     fidx=0;


int v4l_init(int width, int height)
{
        int     cap_type;
        #ifndef NEW_VERSION		
        struct video_window win;
        #endif		
        struct video_tuner tuner;
        
	video_fd = open("/dev/video0", O_RDWR);

	if(video_fd < 0)
	{
	    printf("open device failure");
	    return -1;
	}
#ifndef NEW_VERSION
	if (ioctl(video_fd, VIDIOCGCAP, &video_cap) < 0) 	/// query video capture capability
	{
		printf("VIDIOCGCAP failure");
		return -1;
	}
    	if (!(video_cap.type & VID_TYPE_CAPTURE)) 
	{
	        printf("Fatal: grab device does not handle capture\n");
	        return -1;
   	}
#endif
    	if (!ioctl(video_fd, VIDIOCGTUNER, &tuner))
	{
        	tuner.mode = VIDEO_MODE_NTSC;
        	ioctl(video_fd, VIDIOCSTUNER, &tuner);
    	}

        #ifndef NEW_VERSION
	win.x = 0;
    	win.y = 0;
    	win.width = width;
    	win.height = height;
    	win.chromakey = -1;
    	win.flags = 0;
    	if (ioctl(video_fd, VIDIOCSWIN, &win)< 0) {
	   printf ("set window error\n");
	   return -1;
	}
        #endif
		
        if ( ioctl(video_fd,VIDIOCGMBUF,&gb_buffers) < 0 )
	{
		printf("capture not support mbuf\n");
		return -1;
	}


	if (0 == mmap_done) {
		mmap_done = 1;
        #ifndef NEW_VERSION
		for(i=0; i<gb_buffers.frames; i++){
	        	gb_buffers.offsets[i] = mmap(0, gb_buffers.size, PROT_READ|PROT_WRITE, MAP_SHARED, video_fd, 0);
	    	        if ((unsigned char*)-1 == gb_buffers.offsets[i])
		        {
		      	 printf("mmap error\n");
	    	  	 return -1;
		         }
		}
        #else

             video_buf_virt = mmap(0, gb_buffers.size, PROT_READ|PROT_WRITE, MAP_SHARED, video_fd, 0);
             //printf("gb_buffers.size=%d\n", gb_buffers.size);
        #endif

              ioctl(video_fd, 0x5679, &ingrab);            //grab type
              ioctl(video_fd, 0x5678, &cap_type);
              //iptv_setting(ingrab);
	}

    	gb_buf.height = height;
    	gb_buf.width = width;
    	gb_buf.format = VIDEO_PALETTE_YUV420P;
    	/*first trigger*/
    	gb_buf.frame = 0;
 
	return 0;

}




int mpeg4_init(video_profile *video_setting)
{
    FMP4_ENC_PARAM  enc_param;
   
    if(fmpeg4_enc_fd == 0)
	    fmpeg4_enc_fd = open( FMPEG4_ENCODER_DEV,O_RDWR );
	
    if(fmpeg4_enc_fd == 0)
    {
	printf("Fail to open %s\n",FMPEG4_ENCODER_DEV);
	return -1;
    }    


    mmap_addr = mmap(0, (video_setting->width * video_setting->height * 3) / 2,PROT_READ|PROT_WRITE,MAP_SHARED,fmpeg4_enc_fd, 0);
    if(mmap_addr <= 0)
        return -1;

	enc_param.u32BitRate=video_setting->bit_rate;
	enc_param.u32FrameWidth=video_setting->width;
	enc_param.u32FrameHeight=video_setting->height;
	enc_param.fFrameRate=(float)video_setting->framerate/(float)video_setting->frame_rate_base;
         enc_param.u32InitialQuant=video_setting->quant;
	enc_param.u32IPInterval=video_setting->gop_size;
	enc_param.bEnable4MV=0;
         enc_param.bH263Quant=1;
    
#if 0 //def H263	
	enc_param.bShortHeader=1;
#else
	enc_param.bShortHeader=0;
#endif
	enc_param.bResyncMarker=0;
	enc_param.u32MaxQuant=video_setting->qmax;
	enc_param.u32MinQuant=video_setting->qmin;    
	
         enc_param.bROIEnable   = video_setting->roi_enable;     //0:disable 1:enable
	enc_param.u32ROIX      = video_setting->roi_x;     // must be a multiple of 16 pixels
         enc_param.u32ROIY      = video_setting->roi_y;     // must be a multiple of 16 pixels
         enc_param.u32ROIWidth  = video_setting->roi_width;   // must be a multiple of 16 pixels
         enc_param.u32ROIHeight = video_setting->roi_height;  // must be a multiple of 16 pixels
         //enc_param.motion_dection_enable=0;


    RateControlInit(&ratec,enc_param.u32BitRate*1000, DELAY_FACTOR, RC_AVERAGE_PERIOD,
                    ARG_RC_BUFFER, get_framerate(enc_param.fFrameRate),
                    enc_param.u32MaxQuant,enc_param.u32MinQuant,enc_param.u32InitialQuant);

    if (  ioctl(fmpeg4_enc_fd,FMPEG4_IOCTL_ENCODE_INIT,&enc_param) < 0)
    {
    	close(fmpeg4_enc_fd);
	    printf("Error to set FMPEG4_IOCTL_INIT\n");
	    return -1;
    }
    
	return 0;
}



int enc_close(video_profile *video_setting)
{
	if(fmpeg4_enc_fd)
		close(fmpeg4_enc_fd);
	fmpeg4_enc_fd = 0;
	free(video_setting->coded_frame);
	return 0;
}

int mpeg4_encode(video_profile *video_setting, unsigned char *frame, void * data)
{
    AVFrame             *pict=(AVFrame *)data;
    Faraday_ENC_FRAME   enc_frame;

    if ( video_setting->roi_enable) {
	enc_frame.roi_X = video_setting->roi_x;
	enc_frame.roi_Y = video_setting->roi_y;
    }
    enc_frame.pu8YFrameBaseAddr = (unsigned char *)pict->data[0];   //input user continued virtual address (Y), Y=0 when NVOP
    enc_frame.pu8UFrameBaseAddr = (unsigned char *)pict->data[1];   //input user continued virtual address (U)
    enc_frame.pu8VFrameBaseAddr = (unsigned char *)pict->data[2];   //input user continued virtual address (V)
    enc_frame.bitstream = frame;  //output User Virtual address   
   	enc_frame.quant = ratec.rtn_quant;
  
    enc_frame.intra = -1;
    if (  ioctl(fmpeg4_enc_fd, FMPEG4_IOCTL_ENCODE_FRAME, &enc_frame) <0)
    {
	    printf("Error to set FMPEG4_IOCTL_ENCODE_FRAME\n");
	    return -1;
    }

    RateControlUpdate(&ratec, enc_frame.quant, enc_frame.length, enc_frame.intra);
    
	video_setting->intra = enc_frame.intra; //the return value in enc_frame.intra

    return enc_frame.length;
}

int encode_video(video_profile *video)
{
	unsigned int        blength;
 	AVFrame             yuv_data;
	uint8_t             *ptr;
    
    
    /* Setup to capture the next frame */
    if ((errno=ioctl(video_fd, VIDIOCMCAPTURE, &gb_buf)) < 0) 
    {
        if (errno == EAGAIN)
            fprintf(stderr,"Cannot Sync\n");
        else
            perror("VIDIOCMCAPTURE");
        return;
    }
   

    while (ioctl(video_fd, VIDIOCSYNC, &frame_num) < 0 &&(errno == EAGAIN || errno == EINTR))
        printf("error\n");
    
    ptr = video_buf_virt + gb_buffers.offsets[frame_num];
    frame_num = gb_buf.frame;

    
    yuv_data.data[0] = (unsigned char *)(ptr);
    
    
    if(video->height % 16 != 0)
        video->height = 16 * ((video->height / 16) + 1 );
    
    if(video->width % 16 != 0)
        video->width = 16 * ((video->width / 16) + 1 );
    
    // UV    	
    yuv_data.data[1] = (unsigned char *) (ptr + (video->width * video->height));
    yuv_data.data[2] = (unsigned char *) (ptr + (video->width * video->height * 5 / 4));
    
    
    blength=mpeg4_encode(&video_setting, out_virt_buffer, (void *)&yuv_data);	

    return blength;	   	
}

#define MP4_ROIX 0
#define MP4_ROIY 0
#define MP4_ENCODE_CNT 10
int main(int argc, char **argv)
{
    int ret;
    int i, width, height;
    int blength;

    if(argc<2) {
        		printf("Usage: \n");
        		printf("     mpeg4_main: initization without ROI\n");
        		printf("     mpeg4_main 1: initization with ROI\n");
    }
	
    if( argv[1] == NULL ) {
	video_setting.roi_enable = 0;
	dout=fopen(dout_name,"wb");
	printf("Use encoder output name %s\n",dout_name);
     } else {
	video_setting.roi_enable = 1;
	dout=fopen(dout_roi_name,"wb");
         printf("Use encoder output name %s\n",dout_roi_name);	
     }
		
    //set the default value
    video_setting.qmax = 31;
    video_setting.qmin = 1;
    video_setting.quant = 0;
    video_setting.bit_rate = 512;
    video_setting.width = 720;
    video_setting.height = 480;
    video_setting.framerate = 15;
    video_setting.frame_rate_base = 1;
    video_setting.gop_size = 60;
	
    video_setting.roi_x = MP4_ROIX;
    video_setting.roi_y = MP4_ROIY;
    video_setting.roi_width = 320;
    video_setting.roi_height = 240; 	
	
    if (	video_setting.roi_enable )
       printf("roi width %d height %d\n", video_setting.roi_width, video_setting.roi_height);
   else
       printf("frame width %d height %d\n", video_setting.width, video_setting.height);	

   //Fix the 7137 issue
   if((ingrab==13)||(ingrab==14)||(ingrab==15)) {
   	if(video_setting.width==720)
            video_setting.width=704;
   }
	
   height = video_setting.height;		
   if(height % 16 != 0)
       height = 16 * ((video_setting.height / 16) + 1 );		
       width = video_setting.width;
   if(width % 16 != 0)
       width = 16 * ((video_setting.width / 16) + 1 );
   //video_setting.ysize = width*height;
   	
   out_virt_buffer = (unsigned char *) malloc(width*height*3/2);	
   	
    if((ret=v4l_init(video_setting.width, video_setting.height)) < 0)
        return -1;

    if(mpeg4_init(&video_setting)<0)
        return -1;
 	
    for( i=0; i < MP4_ENCODE_CNT; i++) {
	if ( video_setting.roi_enable) {
	    video_setting.roi_x =  MP4_ROIX + 16*i;
	    video_setting.roi_y =  MP4_ROIY + 16*i;
	    printf("roi [ %d , %d ] ", video_setting.roi_x, video_setting.roi_y);	
	}
         blength = encode_video(&video_setting);
	printf("Video 0 quant %d blength= %d\n",ratec.rtn_quant,blength);	 
	fwrite((void *)out_virt_buffer,blength,1,dout); 
    }

    fclose(dout);
    enc_close(&video_setting);
    free(out_virt_buffer);	
    close(video_fd);

    return 0;
}
